package mylab.starters.mybatis.plugins;

import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import mylab.base.constants.DbConst;
import mylab.starters.mybatis.OrderlyPlugin;
import mylab.starters.mybatis.support.foreignKey.ForeignKeyException;
import mylab.starters.mybatis.support.foreignKey.ForeignKeyService;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.delete.Delete;
import net.sf.jsqlparser.statement.update.Update;
import net.sf.jsqlparser.statement.update.UpdateSet;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;

import java.sql.SQLException;
import java.util.stream.Stream;

/**
 * 外键检查插件
 */
@Slf4j
public class MyForeignKeyInterceptor implements OrderlyPlugin {

    private final String DELETE_BY_ID = "deleteById";
    private final String DELETE_BATCH_IDS = "deleteBatchIds";
    private final String DELETE_BY_MAP = "deleteByMap";
    private final String DELETE = "delete";
    private final String[] SQL_ID_ALL_DELETE = new String[]{DELETE_BY_ID, DELETE_BATCH_IDS, DELETE_BY_MAP, DELETE};

    private final ForeignKeyService foreignKeyService;

    public MyForeignKeyInterceptor(ForeignKeyService foreignKeyService) {
        this.foreignKeyService = foreignKeyService;
    }

    @Override
    public int getOrder() {
        return OrderlyPlugin.ForeignKey;
    }

    @Override
    public String getDescription() {
        return "外键检查插件";
    }

    @Override
    public boolean willDoUpdate(Executor executor, MappedStatement ms, Object parameter) throws SQLException {
        try {
            SqlCommandType sct = ms.getSqlCommandType();
            String sql = ms.getBoundSql(parameter).getSql();
            Statement statement = CCJSqlParserUtil.parse(sql);

            if (sct == SqlCommandType.UPDATE) {
                Update update = (Update) statement;
                String tableName = update.getTable().getName();
                if (isLogicDelete(update)) {
                    checkForeignKey(ms, parameter, tableName, sql);
                }
            }
            if (sct == SqlCommandType.DELETE) {
                Delete delete = (Delete) statement;
                String tableName = delete.getTable().getName();
                checkForeignKey(ms, parameter, tableName, sql);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }

    }

    void checkForeignKey(MappedStatement ms, Object param, String tableName, String sql) throws ForeignKeyException {
        if (isDeleteMode(ms)) {
            if (foreignKeyService != null) {
                foreignKeyService.checkOnDelete(tableName, param);
            } else {
                log.warn(StrUtil.format("### 存在外键引用关系，但未做外键检查!!!  sql:\r\n{}\r\n###", sql));
            }
        }
    }


    boolean isLogicDelete(Update update) {
        for (UpdateSet us : update.getUpdateSets()) {
            boolean setDeleted = us.getColumns().stream().anyMatch(column -> column.getColumnName().equals(DbConst.COLUMN_DELETED));
            boolean isTrue = us.getExpressions().stream().anyMatch(expression -> expression.toString().equals("1"));
            if (setDeleted && isTrue) {
                return true;
            }
        }
        return false;
    }

    boolean isDeleteMode(MappedStatement mappedStatement) {
        String sqlId = mappedStatement.getId();
        SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
        if (isDeleteSqlId(sqlId)) {
            return SqlCommandType.UPDATE == sqlCommandType || SqlCommandType.DELETE == sqlCommandType;
        }
        return false;
    }

    boolean isDeleteSqlId(String sqlId) {
        return Stream.of(SQL_ID_ALL_DELETE).anyMatch(sqlId::endsWith);
    }

}
